#!/usr/bin/env python
# -*- coding: utf-8 -*-

import re
import signal
import sys
import time
from threading import Thread
from irc.bot import SingleServerIRCBot
import sleekxmpp
if sys.version_info.major >= 3:
    from configparser import SafeConfigParser
else:
    from ConfigParser import SafeConfigParser


class IRCBot:
    def __init__(self, opts, inter):
        self.client = SingleServerIRCBot([(opts['server'], opts['port'])],
                                         opts['nick'], opts['nick'])
        self.conn = self.client.connection
        self.opts = opts
        self.nick = opts['nick']
        self.pure_nick = self.nick
        self.chan = opts['chan']
        self.inter = inter

    def register_handlers(self):
        self.conn.add_global_handler('welcome', self.on_session_start)
        self.conn.add_global_handler('pubmsg', self.on_message)
        self.conn.add_global_handler('action', self.on_message)
        self.conn.add_global_handler('join', self.on_presence)
        self.conn.add_global_handler('part', self.on_presence)
        self.conn.add_global_handler('namreply', self.on_namreply)
        self.conn.add_global_handler('kick', self.on_kick)
        self.conn.add_global_handler('nicknameinuse', self.on_nicknameinuse)

    def join_chan(self):
        self.conn.part(self.chan, message='Replaced by new connection')
        self.conn.join(self.chan)

    def on_session_start(self, conn, event):
        print('Connected to IRC')
        self.join_chan()

    def on_message(self, conn, event):
        nick = event.source.split('!')[0]
        body = ''.join(event.arguments)
        typ = event.type

        if typ == 'action':
            body = '/me ' + body

        self.inter.relay_message('irc', nick, body)

    def on_presence(self, conn, event):
        try:
            typ = event.type
            nick = event.source.nick

            if typ == 'part':
                if nick != self.nick:
                    if nick in self.inter.get_irc_users():
                        self.inter.remove_irc_user(nick)

            else:
                if nick != self.nick:
                    if nick not in self.inter.get_irc_users():
                        self.inter.append_irc_user(nick)
        except Exception as e:
            sys.stderr.write(str(e) + '\n')

    def on_namreply(self, conn, event):
        for nick in event.arguments[2].split():
            if nick != conn.get_nickname():
                self.inter.append_irc_user(nick)

    def on_kick(self, conn, event):
        self.nick = self.pure_nick
        conn.nick(self.nick)
        time.sleep(0.5)
        self.join_chan()

    def on_nicknameinuse(self, conn, event):
        self.nick = self.nick + '_'
        conn.nick(self.nick)

    def send_message(self, msg, lead_prefix='', prefix=''):
        buf = 460
        result = []

        try:
            for line in msg:
                for i in range(0, len(line), buf):
                    result.append(line[i:i + buf])

            self.conn.privmsg(self.chan, lead_prefix + result[0])

            for r in result[1:]:
                time.sleep(0.5)
                self.conn.privmsg(self.chan, prefix + r)

        except Exception as e:
            sys.stderr.write(str(e) + '\n')

    def start(self):
        self.register_handlers()
        self.client.start()


class XMPPBot:
    def __init__(self, opts, inter):
        if sys.version_info.major < 3:
            sleekxmpp.util.misc_ops.setdefaultencoding('utf-8')

        self.client = sleekxmpp.ClientXMPP(opts['jid'], opts['passwd'])
        self.opts = opts
        self.nick = opts['nick']
        self.pure_nick = self.nick
        self.muc = opts['muc']
        self.inter = inter

    def register_handlers(self):
        self.client.add_event_handler('session_start', self.on_session_start)
        self.client.add_event_handler('groupchat_message', self.on_message)
        self.client.add_event_handler('muc::%s::presence' % self.muc,
                                      self.on_presence)

    def join_muc(self):
        muc_plugin = self.client.plugin['xep_0045']

        if self.muc in muc_plugin.getJoinedRooms():
            muc_plugin.leaveMUC(self.muc, self.nick,
                                msg='Replaced by new connection')
        muc_plugin.joinMUC(self.muc, self.nick, wait=True)

    def on_session_start(self, event):
        print('Connected to XMPP')
        self.client.get_roster()
        self.client.send_presence()
        self.join_muc()

    def on_message(self, event):
        body = event['body']
        nick = event['mucnick']

        self.inter.relay_message('xmpp', nick, body)

    def on_presence(self, event):
        try:
            muc_plugin = self.client.plugin['xep_0045']
            typ = event['muc']['type']
            nick = event['muc']['nick']

            if not typ:
                typ = event['type']
            if not nick:
                nick = muc_plugin.getNick(self.muc, event['from'])

            if typ == 'error':
                if event['error']['code'] == '409':
                    self.nick = self.nick + '_'
                self.join_muc()

            elif typ == 'unavailable':
                if nick != self.nick:
                    if nick in self.inter.get_xmpp_users():
                        self.inter.remove_xmpp_user(nick)
                else:
                    self.nick = self.pure_nick
                    time.sleep(0.5)
                    self.join_muc()

            else:
                if nick != self.nick:
                    if nick not in self.inter.get_xmpp_users():
                        self.inter.append_xmpp_user(nick)

        except Exception as e:
            sys.stderr.write(str(e) + '\n')

    def send_message(self, msg, prefix=''):
        try:
            msg[0] = prefix + msg[0]
            result = '\n'.join(msg)
            self.client.send_message(mto=self.muc, mbody=result,
                                     mtype='groupchat')

        except Exception as e:
            sys.stderr.write(str(e) + '\n')

    def start(self):
        self.client.register_plugin('xep_0045')  # XMPP MUC.
        self.client.register_plugin('xep_0199')  # XMPP Ping.
        if self.client.connect():
            # sys.stderr.write('connected with %s\n'%con)
            self.register_handlers()
            self.client.process(block=True)
        else:
            # sys.stderr.write('could not connect!\n')
            sys.stderr.write('Could not connect to server, or password ' +
                             'mismatch!\n')
            sys.exit(1)


class Intermedia:
    def __init__(self, shared_opts, irc_chan, xmpp_muc):
        self.irc_chan = irc_chan
        self.xmpp_muc = xmpp_muc
        self.ircbot = None
        self.xmppbot = None
        self.irc_users = []
        self.xmpp_users = []

        self.prefix = shared_opts['prefix']
        self.owner = shared_opts['owner']

    def set_bots(self, ircbot, xmppbot):
        self.ircbot = ircbot
        self.xmppbot = xmppbot

    def to_irc(self, msg, lead_prefix='', prefix=''):
        if self.ircbot:
            self.ircbot.send_message(msg, lead_prefix, prefix)

    def to_xmpp(self, msg, prefix=''):
        if self.xmppbot:
            self.xmppbot.send_message(msg, prefix)

    def relay_message(self, from_net, nick, body):
        if not self.ircbot or not self.xmppbot:
            return

        if from_net != 'irc' and from_net != 'xmpp':
            return

        if from_net == 'irc' and nick == self.ircbot.nick or \
           from_net == 'xmpp' and nick == self.xmppbot.nick:
            return

        if not body or len(body) <= 0:
            return

        try:
            msg = body.replace('\r\n', '\n').replace('\r', '\n').split('\n')

            if msg and len(msg) > 0:
                if len(msg) == 1 and msg[0] == self.prefix + 'users':
                    irc_users = ', '.join(self.get_irc_users())
                    xmpp_users = ', '.join(self.get_xmpp_users())

                    if irc_users:
                        irc_users = '[ IRC Users ] ' + irc_users
                    if xmpp_users:
                        xmpp_users = '[ XMPP Users ] ' + xmpp_users

                    if from_net == 'irc':
                        for answer in [xmpp_users]:
                            self.to_irc([answer])
                    elif from_net == 'xmpp':
                        for answer in [irc_users]:
                            self.to_xmpp([answer])

                elif len(msg) == 1 and msg[0] == self.prefix + 'help':
                    answer = 'The only command I have is \'' + self.prefix + \
                      'users\'. Also, my owner is ' + self.owner + '.'

                    if from_net == 'irc':
                        self.to_irc([answer])
                    elif from_net == 'xmpp':
                        self.to_xmpp([answer])

                else:
                    nick_prefix = '[' + nick + '] '
                    nick_prefix_me = '***' + nick + ' '

                    if (not re.match('^/me .+$', msg[0])):
                        nick_prefix_lead = nick_prefix
                    else:
                        msg[0] = re.split('^/me ', msg[0])[1]
                        nick_prefix_lead = nick_prefix_me

                    if from_net == 'irc':
                        self.to_xmpp(msg, prefix=nick_prefix_lead)
                    elif from_net == 'xmpp':
                        self.to_irc(msg,
                                    lead_prefix=nick_prefix_lead,
                                    prefix=nick_prefix)

        except Exception as e:
            sys.stderr.write(str(e) + '\n')

    def get_irc_users(self):
        return self.irc_users

    def append_irc_user(self, user):
        self.irc_users.append(user)

    def remove_irc_user(self, user):
        self.irc_users.remove(user)

    def get_xmpp_users(self):
        return self.xmpp_users

    def append_xmpp_user(self, user):
        self.xmpp_users.append(user)

    def remove_xmpp_user(self, user):
        self.xmpp_users.remove(user)

if __name__ == '__main__':
    config = SafeConfigParser()
    shared_opts = {}
    xmpp_opts = {}
    irc_opts = {}

    if len(sys.argv) > 1:
        config.read(sys.argv[1])
    else:
        config.read('config.ini')

    if not config.sections():
        sys.stderr.write('Error: Configuration file does not exist ' +
                         'or is empty.\n')
        sys.exit(1)

    shared_opts['prefix'] = config.get('Shared', 'prefix')
    shared_opts['owner'] = config.get('Shared', 'owner')

    irc_opts['chan'] = config.get('IRC', 'channel')
    irc_opts['nick'] = config.get('IRC', 'nick')
    irc_opts['server'] = config.get('IRC', 'server')
    irc_opts['port'] = int(config.get('IRC', 'port'))

    xmpp_opts['jid'] = config.get('XMPP', 'jid')
    xmpp_opts['passwd'] = config.get('XMPP', 'password')
    xmpp_opts['muc'] = config.get('XMPP', 'muc')
    xmpp_opts['nick'] = config.get('XMPP', 'nick')

    signal.signal(signal.SIGINT, signal.SIG_DFL)

    while True:
        inter = Intermedia(shared_opts, irc_opts['chan'], xmpp_opts['muc'])
        ircbot = IRCBot(irc_opts, inter)
        xmppbot = XMPPBot(xmpp_opts, inter)
        inter.set_bots(ircbot, xmppbot)

        irc_thread = Thread(target=ircbot.start, args=())
        xmpp_thread = Thread(target=xmppbot.start, args=())

        irc_thread.daemon = True
        xmpp_thread.daemon = True

        irc_thread.start()
        time.sleep(1)
        xmpp_thread.start()

        irc_thread.join()
        xmpp_thread.join()
